/*
 * Wazuh Vulnerability scanner - Scan Orchestrator
 * Copyright (C) 2015, Wazuh Inc.
 * March 4, 2024.
 *
 * This program is free software; you can redistribute it
 * and/or modify it under the terms of the GNU General Public
 * License (version 2) as published by the FSF - Free Software
 * Foundation.
 */

#ifndef _SCAN_OS_ALERT_DETAILS_BUILDER_HPP
#define _SCAN_OS_ALERT_DETAILS_BUILDER_HPP

#include "chainOfResponsability.hpp"
#include "databaseFeedManager.hpp"
#include "descriptionsHelper.hpp"
#include "fieldAlertHelper.hpp"
#include "numericHelper.h"
#include "scanContext.hpp"

/**
 * @brief TScanOsAlertDetailsBuilder class.
 * This class is responsible for building the alert details for the OS scan.
 * It receives the scan context and the database feed manager and returns the scan context with the alert details.
 * It will generate alerts for detected vulnerabilities in the OS.
 * Its neccessary to have the context->m_elements populated with the cve and the operation.
 * The operation can be "DELETED" or "INSERTED".
 * Its also necessary to have the context->m_matchConditions populated with the cve and the condition.
 * The condition can be "LessThanOrEqual", "LessThan", "DefaultStatus" or "Equal".
 * The alert details will be populated in the context->m_alerts.
 *
 * @tparam TDatabaseFeedManager database feed manager type.
 * @tparam TScanContext scan context type.
 * @tparam TGlobalData global data type.
 */
template<typename TDatabaseFeedManager = DatabaseFeedManager,
         typename TScanContext = ScanContext,
         typename TGlobalData = GlobalData>
class TScanOsAlertDetailsBuilder final : public AbstractHandler<std::shared_ptr<TScanContext>>
{
private:
    std::shared_ptr<TDatabaseFeedManager> m_databaseFeedManager;

public:
    // LCOV_EXCL_START
    /**
     * @brief ScanOsAlertDetailsBuilder constructor.
     *
     * @param databaseFeedManager Database feed manager.
     */
    explicit TScanOsAlertDetailsBuilder(std::shared_ptr<TDatabaseFeedManager>& databaseFeedManager)
        : m_databaseFeedManager(databaseFeedManager)
    {
    }

    /**
     * @brief Class destructor.
     *
     */
    ~TScanOsAlertDetailsBuilder() = default;
    // LCOV_EXCL_STOP

    /**
     * @brief Handles request and passes control to the next step of the chain.
     *
     * @param context Scan context.
     * @return std::shared_ptr<ScanContext> Abstract handler.
     */
    std::shared_ptr<TScanContext> handleRequest(std::shared_ptr<TScanContext> context) override
    {
        // We only generate alerts if its not the first scan(baseline).
        if (!context->m_isFirstScan)
        {
            const auto descriptionSources =
                DescriptionsHelper::cvssAndDescriptionSources<TGlobalData>(context->m_vulnerabilitySource);
            for (const auto& [cve, elementData] : context->m_elements)
            {
                const auto& operation = elementData.at("operation").template get_ref<const std::string&>();
                const auto elementOperation {operation.compare("DELETED") == 0    ? ElementOperation::Delete
                                             : operation.compare("INSERTED") == 0 ? ElementOperation::Insert
                                                                                  : ElementOperation::Unknown};

                try
                {
                    DescriptionsHelper::vulnerabilityDescription<TDatabaseFeedManager, TGlobalData>(
                        cve,
                        descriptionSources,
                        m_databaseFeedManager,
                        [&](const CveDescription& description)
                        {
                            const std::string cvssVersion {description.scoreVersion};
                            const std::string scoreVersion {"cvss" + cvssVersion.substr(0, 1)};
                            nlohmann::json resultData;

                            if (elementOperation == ElementOperation::Insert)
                            {
                                if (!cvssVersion.empty())
                                {
                                    nlohmann::json cvssVector;
                                    if (scoreVersion.compare("cvss2") == 0)
                                    {
                                        cvssVector["access_complexity"] = description.accessComplexity;
                                        cvssVector["authentication"] = description.authentication;
                                    }
                                    else if (scoreVersion.compare("cvss3") == 0)
                                    {
                                        cvssVector["attack_vector"] = description.attackVector;
                                        cvssVector["privileges_required"] = description.privilegesRequired;
                                        cvssVector["scope"] = description.scope;
                                        cvssVector["user_interaction"] = description.userInteraction;
                                    }

                                    cvssVector["availability"] = description.availabilityImpact;
                                    cvssVector["confidentiality_impact"] = description.confidentialityImpact;
                                    cvssVector["integrity_impact"] = description.integrityImpact;

                                    resultData["vulnerability"]["cvss"][scoreVersion]["vector"] = std::move(cvssVector);
                                }

                                resultData["vulnerability"]["assigner"] = description.assignerShortName;
                                resultData["vulnerability"]["cwe_reference"] = description.cweId;
                                resultData["vulnerability"]["package"]["source"] = "OS";
                                resultData["vulnerability"]["rationale"] = description.description;

                                resultData["vulnerability"]["status"] = "Active";

                                const auto generateTitle = [&context, &cve]()
                                {
                                    std::string title {cve};
                                    title.append(" affects ");
                                    title.append(context->osName());
                                    return title;
                                };
                                resultData["vulnerability"]["title"] = generateTitle();
                            }
                            else if (elementOperation == ElementOperation::Delete)
                            {
                                resultData["vulnerability"]["status"] = "Solved";

                                const auto generateTitle = [&context, &cve]()
                                {
                                    std::string title {cve};
                                    title.append(" affecting ");
                                    title.append(context->osName());
                                    title.append(" was solved");
                                    return title;
                                };
                                resultData["vulnerability"]["title"] = generateTitle();
                            }
                            else
                            {
                                throw std::runtime_error("Unknown operation");
                            }

                            resultData["vulnerability"]["cve"] = cve;
                            resultData["vulnerability"]["scanner"]["reference"] = WAZUH_CTI_CVES_URL + cve;
                            if (!cvssVersion.empty())
                            {
                                resultData["vulnerability"]["cvss"][scoreVersion]["base_score"] =
                                    Utils::floatToDoubleRound(description.scoreBase, 2);
                            }
                            resultData["vulnerability"]["enumeration"] = "CVE";
                            resultData["vulnerability"]["package"]["architecture"] = context->osArchitecture();
                            resultData["vulnerability"]["package"]["name"] = context->osName();
                            resultData["vulnerability"]["package"]["version"] = context->osVersion();
                            resultData["vulnerability"]["published"] = description.datePublished;
                            resultData["vulnerability"]["reference"] = description.reference;
                            resultData["vulnerability"]["severity"] = FieldAlertHelper::fillEmptyOrNegative(
                                Utils::toSentenceCase(description.severity.data()));
                            resultData["vulnerability"]["classification"] =
                                FieldAlertHelper::fillEmptyOrNegative(description.classification);
                            resultData["vulnerability"]["score"]["base"] = FieldAlertHelper::fillEmptyOrNegative(
                                Utils::floatToDoubleRound(description.scoreBase, 2));
                            resultData["vulnerability"]["score"]["version"] =
                                FieldAlertHelper::fillEmptyOrNegative(description.scoreVersion);

                            resultData["vulnerability"]["type"] = "Packages";
                            resultData["vulnerability"]["updated"] = description.dateUpdated;

                            if (auto it = context->m_matchConditions.find(cve); it != context->m_matchConditions.end())
                            {
                                if (it->second.condition == MatchRuleCondition::LessThanOrEqual)
                                {
                                    resultData["vulnerability"]["package"]["condition"] =
                                        "Package less than or equal to " + it->second.version;
                                }
                                else if (it->second.condition == MatchRuleCondition::LessThan)
                                {
                                    resultData["vulnerability"]["package"]["condition"] =
                                        "Package less than " + it->second.version;
                                }
                                else if (it->second.condition == MatchRuleCondition::DefaultStatus)
                                {
                                    resultData["vulnerability"]["package"]["condition"] = "Package default status";
                                }
                                else if (it->second.condition == MatchRuleCondition::Equal)
                                {
                                    resultData["vulnerability"]["package"]["condition"] =
                                        "Package equal to " + it->second.version;
                                }
                                else
                                {
                                    logDebug2(WM_VULNSCAN_LOGTAG, "Unknown match condition");
                                }
                            }

                            context->m_alerts[cve] = std::move(resultData);
                        });
                }
                catch (const std::exception& e)
                {
                    logError(WM_VULNSCAN_LOGTAG, "Error building event details for CVE: %s", cve.data());
                    logError(WM_VULNSCAN_LOGTAG, "Error message: %s", e.what());
                }
            }
        }
        return AbstractHandler<std::shared_ptr<TScanContext>>::handleRequest(std::move(context));
    }
};

using ScanOsAlertDetailsBuilder = TScanOsAlertDetailsBuilder<>;

#endif // _SCAN_OS_ALERT_DETAILS_BUILDER_HPP
